using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Text;
using System.Threading;

namespace YouTubeDupe
{
    /// <summary>
    /// The SearchServer module of the YouTube Duplicate Video Finder.
    /// </summary>
    public class SearchServer
    {
        SessionController _controller;

        string _pageSource;

        HttpListener _pageServer;
        HttpListener _functionServer;

        Thread _functionServerThread;
        Thread _pageServerThread;

        int totalVideoCount = 0;
        
        /// <summary>
        /// Instantiates an instance of the search server module.
        /// </summary>
        /// <param name="controller">The controller for module.</param>
        public SearchServer(SessionController controller)
        {
            if (controller == null)
            {
                throw new NullReferenceException("Controller can't be null.");
            }

            _controller = controller;

            // Check if the connection exists.
            if (!YouTubeAPI.ConnectionExists)
            {
                //
                // Implement a prettier error message.
                //
                _pageSource = "YouTube API can't be reached.";
            }
            else
            {
                // If a connection exists, configure the function server and generate the page source.
                _pageSource = Resources.SearchServerPageSource;

                // Configure the function server.
                _functionServer = new HttpListener();
                _functionServer.Prefixes.Add("http://localhost/" + _controller.SessionID + "/searchServer/functionServer/");
                _functionServerThread = new Thread(new ThreadStart(functionServerProcess));
            }

            // Configure the page server.
            _pageServer = new HttpListener();
            _pageServer.Prefixes.Add("http://localhost/" + _controller.SessionID + "/searchServer/pageServer/");
            _pageServerThread = new Thread(new ThreadStart(pageServerProcess));
        }

        /// <summary>
        /// Starts the module and accept incoming requests.
        /// </summary>
        public void Start()
        {
            // Start the http listeners.
            _functionServer.Start();
            _pageServer.Start();

            // Start the http listener threads.
            _functionServerThread.Start();
            _pageServerThread.Start();

            // Open the search web page.
            System.Diagnostics.Process.Start("http://localhost/" + _controller.SessionID + "/searchServer/pageServer/");
        }

        /// <summary>
        /// Stops the module and rejects all incoming requests.
        /// </summary>
        public void Stop()
        {
            // Stop the http listeners.
            _pageServer.Abort();
            _functionServer.Abort();

            // Stop all threads.
            _pageServerThread.Abort();
            _functionServerThread.Abort();
        }

        /// <summary>
        /// The function server process. Accepts incoming http requests and sends appropriate responses.
        /// </summary>
        private void functionServerProcess()
        {
            while (true)
            {
                // Get an incoming request.
                HttpListenerContext context = _functionServer.GetContext();
                
                // Get the function name of the request.
                string functionName = context.Request.QueryString["function"];
                
                // Return if no function name is specified.
                if (functionName == null) return;

                context.Response.AppendHeader("Cache-Control", "no-cache");

                switch (functionName)
                {
                    case "checkId":
                        {
                            // Get the developer id.
                            string developerId = context.Request.QueryString["devId"];

                            // Return if no developer id is specified.
                            if (developerId == null) return;
                            
                            // Check the developer id.
                            bool isValidId = YouTubeAPI.CheckDeveloperID(developerId);
                            
                            // Write the response stream.
                            using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                            {
                                if (isValidId)
                                {
                                    OnPropertyChanged(new PropertyChangedEventArgs("DeveloperID", developerId));
                                    writer.Write("valid id");
                                }
                                else
                                {
                                    writer.Write("invalid id");
                                }
                                writer.Close();
                            }
                            break;
                        }
                    case "getVideoCount":
                        {
                            // Get the video count.
                            // Get the video search tag.
                            string searchTag = context.Request.QueryString["tag"];

                            // Return if no search tag is specified.
                            if (searchTag == null) return;

                            // Get the video count.
                            totalVideoCount = YouTubeAPI.GetVideoCount(searchTag, _controller.DeveloperID);
                            
                            // Write the response.
                            using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                            {
                                if (totalVideoCount > 0)
                                {
                                    // Trigger property changes.
                                    OnPropertyChanged(new PropertyChangedEventArgs("SearchTerm", searchTag));
                                    OnPropertyChanged(new PropertyChangedEventArgs("UserSpecifiedVideoCount", totalVideoCount));
                                    int tolerance = -1;
                                    try
                                    {
                                        tolerance = int.Parse(context.Request.QueryString["tolerance"].Replace("%", ""));
                                        OnPropertyChanged(new PropertyChangedEventArgs("Tolerance", tolerance));
                                    }
                                    catch (FormatException) { }
                                }
                                writer.Write(totalVideoCount);
                                writer.Close();
                            }
                            break;
                        }
                    case "setVideoCount":
                        {
                            // Set the user specified video count.
                            int userSpecifiedVideoCount = -1;
                            try
                            {
                                userSpecifiedVideoCount = int.Parse(context.Request.QueryString["count"]);

                                if (userSpecifiedVideoCount > totalVideoCount || userSpecifiedVideoCount < 1)
                                {
                                    throw new FormatException();
                                }

                                OnPropertyChanged(new PropertyChangedEventArgs("UserSpecifiedVideoCount", userSpecifiedVideoCount));
                            }
                            catch (FormatException)
                            {
                                // Write an error message as the response if the number is invalid.
                                using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                                {
                                    writer.Write("invalid number");
                                    writer.Close();
                                }
                            }
                            break;
                        }
                    case "moveNext":
                        {
                            // Move to the next module.
                            OnModuleChanging(EventArgs.Empty);
                            break;
                        }
                }

                // Close the response.
                context.Response.Close();
            }
        }

        /// <summary>
        /// The page server process. Accepts incoming http requests and sends the html page back.
        /// </summary>
        private void pageServerProcess()
        {
            while (true)
            {
                try
                {
                    HttpListenerContext context = _pageServer.GetContext();
                    context.Response.AppendHeader("Cache-Control", "no-cache");
                    using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                    {
                        writer.Write(_pageSource);
                        writer.Close();
                    }
                    context.Response.Close();
                }
                // The catchers below are needed for when this process's thread is stopped.
                catch (HttpListenerException) { }
                catch (ObjectDisposedException) { }
            }
        }

        /// <summary>
        /// Triggered when a property needs to change in the controller.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Handes the PropertyChanged event.
        /// </summary>
        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        /// <summary>
        /// Triggered when module control is changing.
        /// </summary>
        public event EventHandler ModuleChanging;

        /// <summary>
        /// Handles the ModuleChanging event.
        /// </summary>
        private void OnModuleChanging(EventArgs e)
        {
            if (ModuleChanging != null)
            {
                ModuleChanging(this, e);
            }
        }
    }

    /// <summary>
    /// The delegate used to handle the PropertyChanged event.
    /// </summary>
    public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);

    /// <summary>
    /// The class used to provied arguments for the PropertyChanged event.
    /// </summary>
    public class PropertyChangedEventArgs
    {
        string _propertyName;
        object _propertyValue;

        /// <summary>
        /// Creates an instance of the PropertyChangedEventArgs class.
        /// </summary>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="propertyValue">The value of the property.</param>
        public PropertyChangedEventArgs(string propertyName, object propertyValue)
        {
            _propertyName = propertyName;
            _propertyValue = propertyValue;
        }

        /// <summary>
        /// The name of the property that changed.
        /// </summary>
        public string PropertyName
        {
            get { return _propertyName; }
        }

        /// <summary>
        /// The value of the property that changed.
        /// </summary>
        public object PropertyValue
        {
            get { return _propertyValue; }
        }
    }
}